// 编译器默认需要选项
var baseOptions = {
  expectHTML: true, // 
  modules: [], // 静态的字符选项
  directives: {}, // model html text 函数
  isPreTag: function () { },
  isUnaryTag: function () { }, // 检测元素是否单标签
  mustUseProp: function () { }, // 是否进行props 绑定
  canBeLeftOpenTag: function () { }, // 是否可以补全闭合的标签
  isReservedTag: function () { }, // 是否为保留标签
  getTagNamespace: function () { }, // 获取当前元素的命名空间
  staticKeys: function () { } // 根据编译器选项的modules 生成静态链的字符串
};
var noop = function () { }

function warn$2(msg) {
  console.error("[Vue Complier]" + msg);
}

var no = function (a, b, c) {
  return false;
}

var isPlainTextElement = makeMap('script,style,textarea', true)

// 检测key 是否在makeMap
function makeMap(str, expectsLowerCase) {
  var map = {};
  var list = str.split(",");
  for (var i = 0; i < list.length; i++) {
    map[list[i]] = true;
  }

  return expectsLowerCase ? function (val) {
    return map[val.toLowerCase()];
  } : function (val) {
    return map[val];
  }
}

var isUnaryTag = makeMap(
	'area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' +
	'link,meta,param,source,track,wbr'
);

// 函数体字符串
function createFunction(code, errors) {
	try {
		return new Function(code)
	} catch (err) {
		errors.push({
			err: err,
			code: code
		});
		return noop
	}
}
var div;

function getShouldDecode(href) {
  div = div || document.createElement('div');
  div.innerHTML = href ? "<a href=\"\n\"/>" : "<div a=\"\n\"/>";
  return div.innerHTML.indexOf('&#10;') > 0
}
var inBrowser = typeof window !== 'undefined';
// #3663: IE encodes newlines inside attribute values while other browsers don't
// 监听所有属性
var shouldDecodeNewlines = inBrowser ? getShouldDecode(false) : false;
// #6828: chrome encodes content in a[href]
// 监听href属性
var shouldDecodeNewlinesForHref = inBrowser ? getShouldDecode(true) : false;


function createCompileToFunctionFn(Compile) {
  // 缓存对象 存储编译的结果
  var cache = Object.create(null);
  return function compileToFunctions(template, options, vm) {

    // 检查安全策略环境是否支持模板编译器
    try {
      new Function('return 1');
    } catch (e) {
      if (e.toString().match(/unsafe-eval|CSP/)) {
        console.error("当前环境禁止不安全评估的内容安全策略，模板编译器无法在此环境下工作");
      }
    }

    // 检查缓存对象
    var key = template;
    if (cache[key]) {
      return cache[key];
    }

    // 真正的编译工作都是依赖与这个compile 函数
    var compiled = Compile(template, options);
    // compiled.errors 错误信息  compiled.tips 提示信息
    if (compiled.errors && compiled.errors.length) {
      console.error("错误信息");
    }
    if (compiled.tips && compiled.tips.length) {
      console.error("提示信息");
    }

  }
}

function createCompilerCreator(baseCompile) {
  return function createCompiler(baseOptions) {
    // 定义一个Compile 函数
    function Compile(template, options) {
      // finalOptions 最终的选项
      var finalOptions = Object.create(baseOptions);
      var errors = [];
      var tips = [];

      // 错误提示收集
      finalOptions.warn = function (msg, tip) {
        (tip ? tips : errors).push(msg);
      }

      if (options) {
        // 检查用户有哪些自定义的配置，最终扩展到 finalOptions
        // 合并自定义 directives
        if (options.modules) {
        }
        // 合并自定义 directives
        if (options.directives) {
        }
        // copy other options
        for (var key in options) {
          if (key !== 'modules' && key !== 'directives') {
            finalOptions[key] = options[key];
          }
        }
      }

      var compiled = baseCompile(template, finalOptions);
      // detectErrors 收集 compiled.ast 错误&提示信息 
      // errors.push.apply(errors, detectErrors(compiled.ast));
      compiled.errors = errors;
      compiled.tips = tips;

      var res = {};
      var fnGenErrors = [];
      // 将函数体字符串转化为渲染函数
      res.render = creatreFunction(compiled.render, fnGenErrors);
      // 渲染优化
      res.staticRenderFns = compiled.staticRenderFns.map(function (code) {
        return creatreFunction(code, fnGenErrors);
      });

      return res;
    }
    return {
      compile: Compile,
      compileToFunctions: createCompileToFunctionFn(Compile)
    }
  }
}

// createCompilerCreator 编译器的创建者
var createCompiler = createCompilerCreator(function baseCompile(template, options) {
  /**
   * parse() => 把模板解析成抽象语法树 AST
   * generate() => 根据给定的AST 生成目标平台需要的代码”函数体字符串“
   */
  var ast = parse(template.trim(), options);
  // var code = generate(ast, options); // 函数体字符串

  return {
    //  ast: ast,
    //  render: code.render, // 函数体字符串
    //  staticRenderFns: code.staticRenderFns // 渲染优化
    render: "code.render", // 函数体字符串
    staticRenderFns: []
  }
})

// createCompiler 创建一个编译器
var ref$1 = createCompiler(baseOptions);
var compileToFunctions = ref$1.compileToFunctions;

// parse 解析 parser解析器
function parse(template, options) {
  // 模板字符串进行词法分析
  parseHTML(template, {
    warn: warn$2,
    expectHTML: options.expectHTML, //true
    isUnaryTag: options.isUnaryTag, //检测元素是否为单标签
    canBeLeftOpenTag: options.canBeLeftOpenTag, //   是否可以补全闭合的标签
    shouldDecodeNewlines: options.shouldDecodeNewlines,
    shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,
    shouldKeepComment: options.comments,
    start: function start(tag, attrs, unary) { }
  });
}

// 用于分析标记和属性
var attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
var comment = /^<!\--/; //注释节点
var conditionalComment = /^<!\[/; //条件注释
var doctype = /^<!DOCTYPE [^>]+>/i; //doctype
var ncname = '[a-zA-Z_][\\w\\-\\.]*'; //  XML  <前缀+标签名>
var qnameCapture = "((?:" + ncname + "\\:)?" + ncname + ")";
var startTagOpen = new RegExp(("^<" + qnameCapture));
var startTagClose = /^\s*(\/?)>/; //有没有属性  end  
var endTag = new RegExp(("^<\\/" + qnameCapture + "[^>]*>"));

var decodingMap = {
	'&lt;': '<',
	'&gt;': '>',
	'&quot;': '"',
	'&amp;': '&',
	'&#10;': '\n',
	'&#9;': '\t'
};
var encodedAttr = /&(?:lt|gt|quot|amp);/g;
var encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#10|#9);/g;

function decodeAttr(value, shouldDecodeNewlines) {
	var re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr;
	return value.replace(re, function(match) {
		return decodingMap[match];
	})
}

function parseHTML(html, options) {
	var stack = []; // stack  作用: 存储    存储啥！标签名
	var expectHTML = options.expectHTML; // true
	var isUnaryTag$$1 = options.isUnaryTag || no; //
	var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no;
	var index = 0; // 字符流的读入位置
	var last, lastTag; // last还未被解析的html字符串  lastTag = 标签名

	while (html) { // 死循环   终止条件
		last = html;
		if (!lastTag || !isPlainTextElement(lastTag)) {
			// 解析的内容不是在纯文本标签(script style textarea)
			var textEnd = html.indexOf('<');
			if (textEnd === 0) {
				// 第一个字符是 < 注释节点
				if (comment.test(html)) {
					var commentEnd = html.indexOf('-->');

					if (commentEnd >= 0) {
						advance(commentEnd + 3); // + (-->)
						continue
					}
				}
				// 第一个字符是 < 条件注释
				if (conditionalComment.test(html)) {
					var conditionalEnd = html.indexOf(']>');

					if (conditionalEnd >= 0) {
						advance(conditionalEnd + 2);
						continue
					}
				}
				// 第一个字符是 < DOCTYPE
				var doctypeMatch = html.match(doctype);
				if (doctypeMatch) {
					advance(doctypeMatch[0].length); // 解析了 切一刀
					continue
				}
        // 第一个字符是 </ 
        var endTagMatch = html.match(endTag);
				//endTagMatch  </div>   []
				if (endTagMatch) {
					var curIndex = index; //初始位置
					advance(endTagMatch[0].length); //切割
					//检测模板的规范性  是否有为完成的闭合标签
					parseEndTag(endTagMatch[1], curIndex, index); //标签名  起始位置  结束位置
					continue
				}
				// 第一个字符是 < 标签    名称
				var startTagMatch = parseStartTag();
				if (startTagMatch) {
					handleStartTag(startTagMatch);
					if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {
						advance(1);
					}
					continue
				}

			}
			if (textEnd > 0) {

			}
			if (textEnd < 0) {

			}
		} else {
			//是在纯文本标签(script style textarea)
		}
  }

  function advance(n) {
    index += n;
    html = html.substring(n);
  }

  // 获取标签名
  function parseStartTag() {
    var start = html.match(startTagOpen); // 数组
    if (start) {
      var match = {
        tagName: start[1], // 标签名
        attrs: [], // 标签属性
        start: index // 字符流位置
      };
      advance(start[0].length);
      var end, attr;
      while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
        advance(attr[0].length); // 解析属性 删除
        match.attrs.push(attr); // 存储
      }
      if (end) {
        match.unarySlash = end[1];
        advance(end[0].length);
        match.end = index;
        return match
      }
    }
  }

  function handleStartTag(match) {
		var tagName = match.tagName;
		var unarySlash = match.unarySlash;

		if (expectHTML) {
			if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
				parseEndTag(lastTag);
			}
			if (canBeLeftOpenTag$$1(tagName) && lastTag === tagName) {
				parseEndTag(tagName);
			}
		}


		//是否为单标签
		var unary = isUnaryTag$$1(tagName) || !!unarySlash;

		var l = match.attrs.length;
		var attrs = new Array(l);
		for (var i = 0; i < l; i++) {
			var args = match.attrs[i];
			var value = args[3] || args[4] || args[5] || '';
			var shouldDecodeNewlines = tagName === 'a' && args[1] === 'href' ?
				options.shouldDecodeNewlinesForHref :
				options.shouldDecodeNewlines;
			attrs[i] = {
				name: args[1],
				value: decodeAttr(value, shouldDecodeNewlines)
			};
		}

		if (!unary) {
			stack.push({
				tag: tagName,
				lowerCasedTag: tagName.toLowerCase(),
				attrs: attrs
			});
			lastTag = tagName;
		}

		if (options.start) {
			options.start(tagName, attrs, unary, match.start, match.end);
		}
	}
    
	//检测模板的规范性  是否有未完成的闭合标签
	function parseEndTag(tagName, start, end) {
		var pos, lowerCasedTagName;   //pos  未完成的闭合标签  
		if (start == null) {
			start = index;
		}
		if (end == null) {
			end = index;
		}

		// div
		if (tagName) {
			lowerCasedTagName = tagName.toLowerCase();  //a
			//stack  ==  1  pos == 1  stack[pos].lowerCasedTag  ===div  === lowerCasedTagName  
			for (pos = stack.length - 1; pos >= 0; pos--) {
				  console.log(div)
				if (stack[pos].lowerCasedTag === lowerCasedTagName) {
					break
				}
			}
		} else {
			// If no tag name is provided, clean shop
			pos = 0;
		}

		if (pos >= 0) {  
			console.log(!tagName)
			//  1   stack[成员1 div, 成员2 a]
			for (var i = stack.length - 1; i >= pos; i--) {
				if (i > pos || !tagName &&
					options.warn
				) {
					options.warn(
						("tag <" + (stack[i].tag) + "> has no matching end tag.")
					);
				}
				if (options.end) {
					options.end(stack[i].tag, start, end);
				}
			}

			// 重置下stack.length   ==  0   stack []
			stack.length = pos;
			lastTag = pos && stack[pos - 1].tag;  //div   
		} else if (lowerCasedTagName === 'br') {
			if (options.start) {
				options.start(tagName, [], true, start, end);
			}
		} else if (lowerCasedTagName === 'p') {
			if (options.start) {
				options.start(tagName, [], false, start, end);
			}
			if (options.end) {
				options.end(tagName, start, end);
			}
		}
	}
}
